﻿#region Legal Disclaimer
// THIS SOFTWARE IS PROVIDED UNDER THIS LICENSE ON AN “AS IS” BASIS, WITHOUT 
// WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT 
// LIMITATION, WARRANTIES THAT THE COVERED SOFTWARE IS FREE OF DEFECTS, 
// MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. THE ENTIRE 
// RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED SOFTWARE IS WITH 
// YOU. SHOULD ANY OF THIS SOFTWARE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT 
// THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY 
// NECESSARY SERVICING, REPAIR OR CORRECTION. NO USE OF THIS SOFTWARE IS 
// AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER.
// 
// Microsoft Public License (Ms-PL) governs use of this software. If you use the software, 
// you accept this license. If you do not accept the license, do not use the software.
#endregion
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text.RegularExpressions;

namespace SimplyConfigured.Sources
{
    /// <summary>
    /// Parse settings from ini-like files
    /// </summary>
    /// <remarks>
    /// Courtesy of Alex Pinsker (http://alexpinsker.blogspot.com/2005/12/reading-ini-file-from-c_113432097333021549.html)
    /// </remarks>
    internal class IniFileAccess
    {
        static private readonly Regex _iniKeyValuePatternRegex = new Regex(
                @"((\s)*(?<Key>([^\=^\s^\n]+))[\s^\n]*
                                            # key part (surrounding whitespace stripped)
                                            \=
                                            (\s)*(?<Value>([^\n^\s]+(\n){0,1})))
                                            # value part (surrounding whitespace stripped)
                                            ",
                RegexOptions.IgnorePatternWhitespace |
                RegexOptions.Compiled |
                RegexOptions.CultureInvariant);
        
        public IniFileAccess(Uri iniFile)
        {
            if (iniFile == null) throw new ArgumentNullException("iniFile", "INI file path cannot be empty or null.");
            
            if (!File.Exists(iniFile.LocalPath))
            {
                throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, "File '{0}' does not exist.", iniFile.LocalPath), "iniFile");
            }

            FilePath = iniFile;
        }

        protected Uri FilePath { get; private set; }

        public Dictionary<string, string> GetData()
        {
            return ParseFileReadValue();
        }

        private static string ConstructKey(string category, string key)
        {
            /* considered putting a separator char between category and key. for that purpose
             *  I initially considered to make the keys look like this:
             *      Category.Key
             * For now -- until we utilize a formatter -- I'll simply leave it as
             *      [Category]Key
            */
            var displayCat = (string.IsNullOrWhiteSpace(category)) ? string.Empty : "";  //category.Substring(1, category.Length - 2)
            
            var formattedKey = string.Format(CultureInfo.CurrentCulture
                                             , "{0}{1}{2}"
                                             , displayCat
                                             , (string.IsNullOrEmpty(displayCat) ? string.Empty : "")
                                             , key);
            return formattedKey;
        }

        private Dictionary<string, string> ParseFileReadValue()
        {
            var list = new Dictionary<string, string>();
            var category = string.Empty;

            using (var reader = new StreamReader(FilePath.LocalPath))
            {
                do
                {
                    var line = reader.ReadLine();

                    if (line == null) continue;

                    line = line.Trim();

                    if (line.StartsWith("[", StringComparison.OrdinalIgnoreCase) && line.EndsWith("]", StringComparison.OrdinalIgnoreCase))
                    {
                        category = line;
                        continue;
                    }

                    ParseConfigItem(list, line, category);
                }
                while (reader.Peek() != -1);
            }

            return list;
        }

        private static void ParseConfigItem(Dictionary<string, string> list, string line, string category)
        {
            if (list == null) throw new ArgumentNullException("list");
            if (line == null) throw new ArgumentNullException("line");
            if (category == null)throw new ArgumentNullException("category");

            var match = _iniKeyValuePatternRegex.Match(line);

            if (match.Success)
            {
                var currentKey = match.Groups["Key"].Value;
                var currentVal = match.Groups["Value"].Value;

                if (!string.IsNullOrWhiteSpace(currentKey))
                {
                    list.Add(ConstructKey(category, currentKey), currentVal);
                }
            }
        }
    }
}